package dreadnought;
import javax.swing.*;
import java.awt.*;
import java.util.*;
/**
 * This abstract class will be the parent of both PlayerGrid and EnemyGrid.
 * Anything that PlayerGrid and EnemyGrid have in common belongs in this class.
 */
public abstract class Grid extends JLayeredPane
{
    protected Game game;
    
    //a 2D array of the cells of the grid
    protected GridCell[][] cells;
    
    //the five ships of the grid
    //both PlayerGrid and EnemyGrid will have five ships, however
    //since enemy ships are not visible, the ships only be added
    //as components to PlayerGrid
    protected Ship[] ships; 
    
    //whether or not the cells of this grid can be clicked
    protected boolean cellsClickable;

    //the coordinate of the last cell which was hit (fired upon)
    public GridCoordinate lastHitCoord;

    //the heuristic index which was assigned to the last GridCell
    //(the one at coordinate J10) plus the heuristic value of that
    //grid cell. when a grid cell is stochastically picked, the random
    //integer index should be between 0 and maxHeuristicIndex
    public int maxHeuristicIndex;

    //this holds the largest heuristic found from the last time that
    //heuristics where updated. it is used to calculate the color
    //of the GridCells so that their color reflects their heuristic.
    public int maxHeuristic;

    //constants used by heuristic functions
    //base number of points for each cell state
    final int HPTS = 40; //points for cell with state HIT
    final int UPTS = 1; //points for cell with state UNKNOWN
    
    //increasing PROXIMITY_EFFECT increases the effect that proximity has
    //on the score added by neighboring cells to a cells heuristic value
    //if this is set to 1, it has no effect. do not set less than 1.
    final int PROXIMITY_EFFECT = 3;

    //number of blocks to look at in each direction when calculating the
    //heuristic value of a cell. total number of blocks examined
    //will be SEARCH_RADIUS * 4 because it will look north, south, east and west
    //if a cell with a state != UNKNOWN is encountered, the search will be
    //ended prematurely.
    final int SEARCH_RADIUS = 2;
    
    protected ImageIcon background;
    
    public Grid(Game game)
    {
        this.game = game;
        
        this.setLayout(null); //disable layout mgr, use absolute positioning
        
        //by default cells are clickable
        //in PlayerGrid, this will be changed to false
        this.cellsClickable = true;
        
        //set the size of the panel in pixels based on
        //the CELL and PAD costants in Game
        //the grid will be square, 10 rows by 10 columns
        //each cell is CELL pixels square
        //with PAD pixels of padding all around it
        int gridSize = game.PAD*2 + game.CELL*10;
        this.setPreferredSize(new Dimension(gridSize,gridSize));

        //this.setBackground(game.COLORS_WATER);
        //this.setOpaque(true);

        //instantiate cells
        this.cells = new GridCell[10][10];
        for (int i=0; i<10; i++)
        {
            for (int j=0; j<10; j++)
            {
                this.cells[i][j] = new GridCell(this.game, this, new GridCoordinate(i,j));
                this.add(this.cells[i][j], new Integer(0));
            }
        }
        
        //instantiate ships
        ships = new Ship[5];
        ships[0] = new Ship(game, ShipType.PATROLBOAT);
        ships[1] = new Ship(game, ShipType.SUBMARINE);
        ships[2] = new Ship(game, ShipType.DESTROYER);
        ships[3] = new Ship(game, ShipType.BATTLESHIP);
        ships[4] = new Ship(game, ShipType.CARRIER);

        //load explosion image
        this.background = new ImageIcon(
                this.getClass().getResource("/dreadnought/ocean.png"));
    }
    
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        g.setColor(Color.pink);

        g.drawImage(this.background.getImage(), game.PAD, game.PAD, null);
        
        g.setColor(Color.black);

        for (int i=0; i<10; i++)
        {
            //draw the row labels 1-10
            g.drawString(Integer.toString(i+1), 3,
                    game.PAD + game.CELL * i + game.CELL/2);

            //draw the column labels A-J
            g.drawString(Character.toString(game.chars[i]),
                    game.PAD + game.CELL * i + game.CELL/2, 15);
        }
    }

    //this method will be overloaded by both PlayerGrid and EnemyGrid
    public void fire(GridCoordinate coord)
    {
        
    }

    //Searches the ships of this grid for one which occupies the
    //given coordinate. If a ship occupying the given coordinate is
    //found, a reference to that ship is returned. If no ship occupies
    //the given coordinate, null is returned.
    public Ship getShipAtCoordinate(GridCoordinate g)
    {
        for (Ship s : this.ships)
        {
            if (s.occupiesCoordinate(g))
                return s;
        }
        return null;
    }

    //returns true if any ships overlap
    public boolean shipsOverlap()
    {
        for (Ship i : this.ships)
        {
            for (Ship j : this.ships)
            {
                if (i != j && i.overlaps(j))
                    return true;
            }
        }
        return false;
    }

    //returns true if ship with given id overlaps with any ships which
    //have an id lower than given id
    //assumes that ships with id < shipId do not overlap with each other
    //or rather does not care if any of the ships with an id < shipId
    //overlap with each other. it only determines if the ship with
    //id shipId overlaps with any ships with a lower id
    public boolean overlapsWithLower(int shipId)
    {
        Ship thisShip = this.ships[shipId];

        //determine if thisShip overlaps with any of the ships
        //which have an id lowever than shipId
        for (int i=0; i<shipId; i++)
        {
            Ship thatShip = this.ships[i];
            if (thisShip.overlaps(thatShip))
                return true;
        }
        return false;
    }

    //gets the GridCell associated with a given GridCoordinate
    public GridCell getCellFromCoordinate(GridCoordinate coord)
    {
        int col = coord.getCol();
        int row = coord.getRow();
        return this.cells[col][row];
    }

    //returns true if all ships have been sunk
    public boolean allShipsSunk()
    {
        for (Ship s : this.ships)
        {
            if (!s.isSunk())
                return false;
        }
        return true;
    }

    //returns a random coordinate
    public GridCoordinate randomCoordinate()
    {
        return new GridCoordinate(
                game.rand.nextInt(10),
                game.rand.nextInt(10));
    }

    //returns a random coordinate which has not been hit
    public GridCoordinate randomUnhitCoordinate()
    {
        GridCoordinate result;
        boolean hit;
        do {
            result = randomCoordinate();
            hit = getCellFromCoordinate(result).getState() != CellState.UNKNOWN;
        } while (hit);
        return result;
    }

    /**
     * Calculates the heuritic estimate of the value for the given
     * {@link GridCoordinate}. A higher heuristic means a greater
     * estimated change that this coordinate is occupied by a ship.
     * Description of heursitic method:
     * The heuristic value of a cell P will be based the state of the
     * cells directly to the north, south, east and west of P.
     * Note that cells can have three possible states:
     * UNKOWN - the cell has not be fired upson
     * HIT - the cell has been fired upon and a ship was hit
     * MISS - the cell has been fired upon and no ship was hit
     * Also note that the longest ship in the game is 5 blocks long.
     * It is for this reason that the heuristic function will only
     * examine four cells in each direction away from P (16 total).
     * For each cell which is examined, a certain number of points
     * will be added (or subtracted) from the heuristic score of P.
     * These "certain numbers" are stored in the following variables:
     * the base number of points for cells with state UNKNOWN
     * the base number of points for cells with state HIT
     * the base number of points for cells with state MISS
     * These base numbers will be multiplied based on the cell's
     * proximity to P. Cells which are immediately adjacent to P
     * will get multiplied by 4, cells which are four blocks away
     * from P will get multiplied by 1, etc...
     * The work of calculating the heuristic will be divided into
     * four parts: the heuristic points obtained from cells to the north,
     * those obtained from the south, the east, and from the west.
     * the total heuristic value = H(north) + H(south) + H(east) + H(west)
     */
    public int getHeuristic(GridCell cell)
    {
        //TODO: finish writing heuristic(GridCoordinate coord)
        
        //if a cell has already been fired upon, its heuristic is 0
        //because it makes no sense to fire a the same cell twice
        if (cell.getState() != CellState.UNKNOWN)
            return 0;

        //shortuts
        int pcol = cell.getAnchor().getCol();
        int prow = cell.getAnchor().getRow();

        //the accumulated heuristic value to be returned
        int hval = 0;

        //get the points obtained by going in each of the four cardinal directions
        hval += getPoints(pcol, prow, 1, 0, SEARCH_RADIUS); //points from east
        hval += getPoints(pcol, prow, -1, 0, SEARCH_RADIUS); //points from west
        hval += getPoints(pcol, prow, 0, -1, SEARCH_RADIUS); //points from north
        hval += getPoints(pcol, prow, 0, 1, SEARCH_RADIUS); //points from south

        //if the accumulated heuristic value from all for directions is ==4
        //then there are zero hit cells anywhere around P
        //in this case we will give P a heuristic value of zero only if
        //it does not match our chessboard pattern
        //to match our chess board patter the following must be true:
        //if the row of P is odd, the col of P should be odd
        //if the row of P is even, the col of P should be even
        //if the rules are not met and P has no nearby hit cells,
        //we give it a heuristic value of 0
        if (hval == 4 && pcol % 2 != prow % 2)
        {
            hval = 0;
        }

        return hval;
    }

    /**
     * This recursive function travels the grid in the provided direction
     * and sums the points from the cells it visits. Recursion ends
     * when distance reaches 1 or when the path of travel leads off
     * of the grid. This function will be used by the
     * heuristic function to calculate the points of the cells in
     * the four cardinal directions. It will be called four times
     * by the heurstic function. hdir, vdir values for cardinal directions:
     * north=(0,-1) south=(0,1) east=(-1,0) west=(1,0)
     * When called by the heuristic function, dist will initially be 4.
     * @param vdir the vertical direction of travel, 1 or 0 or -1
     * @param hdir the horizontal direction of travel, 1 or 0 or -1
     * @param dist the distance to travel, initially 4, decreases to 1
     * @return the sum of the points for all cells in the direction
     */
    private int getPoints(int col, int row, int hdir, int vdir, int dist)
    {
        //deterine if the current cell is still on the grid
        //if it isn't, this function should return 0
        //also this function should return 0 if dist<0
        if (col<0 || col>9 || row<0 || row>9 || dist<0)
            return 1;

        //if the state of this cell is a miss, this recursion should end
        if (this.cells[col][row].getState() == CellState.MISS)
            return 1;

        //the heuristic points obtained from this cell
        int points = 1;

        //if distance equals SEARCH_RADIUS, then this is the first call of getPoints
        //and row and col are the coordinates of P. P itself should not
        //constribute to its own heuristic value so we should just move
        //on to the next cell in the travel direction

        if (dist != SEARCH_RADIUS)
        {
            //get the GridCell object for the current cell
            GridCell cell = this.cells[col][row];
            
            //get the points associated with the current cell
            if (cell.getState() == CellState.HIT)
            {
                //if the ship at this cell has already been sunk,
                //then the cells does get HPTS points.
                //instead it is treated just like a miss
                //(returns 1, recursion ends)

                //determine which ship is at this cell
                Ship s = this.getShipAtCoordinate(cell.getAnchor());

                if (s.isSunk())
                    return 1;

                points = HPTS;
            }
            else if (cell.getState() == CellState.UNKNOWN)
            {
                points = UPTS;
            }

            //points = basePoints * dist+1 * this.PROXIMITY_EFFECT;

        }

        //return the points gained from the current cell plus
        //the points gained from the rest of the cells in the travel
        //direction (recursively)
        return points * getPoints(col+hdir, row+vdir, hdir, vdir, dist-1);
    }

    /**
     * Uses heuristic functions to stochastically choose a target coordinate
     * for the enemy to fire at which seems to show a high probability of a
     * ship occupying it.
     * @return a stochastically selected coordinate
     */
    public GridCoordinate stochasticallyChooseTarget()
    {
        //update the heuristic indices for each cell
        updateHeuristicIndices();
        //generate a random index
        int randIndex = game.rand.nextInt(this.maxHeuristicIndex);
        //find the last cell cell which has an index less than randIndex
        //this is done by finding the first cell which has an index greater
        //than randIndex and returning the one immediately before that one.
        //the exeption to this, is if the last cell is reached.
        //if the last cell is reached, simply return the last cell
        GridCell prevCell = this.cells[0][0];
        for (int i=0; i<10; i++)
        {
            for (int j=0; j<10; j++)
            {
                GridCell cell = this.cells[i][j];

                //see if this cell has index > randIndex
                if (cell.heuristicIndex > randIndex)
                {
                    return prevCell.getAnchor();
                }

                //only set the previous cell to be the current cell if the
                //current cell has a heuristic value greater than 0
                //because heuristic value of 0 means that the cell has already
                //been fired upon
                if (cell.heuristicValue > 0)
                {
                    prevCell = cell;
                }
            }
        }
        //if control makes it to this point without returning a cell
        //then the last cell (J10) will be returned.
        return prevCell.getAnchor();
    }

    /**
     * updates the heuristicIndex property of each GridCell based on
     * each cell's heuristic estimate so that one can be stochastically selected
     */
    public void updateHeuristicIndices()
    {
        this.maxHeuristic = 0;
        int currentIndex = 0;
        for (int i=0; i<10; i++)
        {
            for (int j=0; j<10; j++)
            {
                GridCell cell = this.cells[i][j];
                int hval = cell.updateHeuristic();
                if (hval > this.maxHeuristic)
                    this.maxHeuristic = hval;
                //if the cells heuristic value is 0, it should not even
                //be a candidate for selection because it's state is not
                //UNKNOWN. In this case, set it's heuristic estimate to
                //the sentinel value -1
                if (hval == 0)
                    cell.heuristicIndex = -1;
                else
                {
                    cell.heuristicIndex = currentIndex;
                    currentIndex += hval;
                }
            }
        }
        this.maxHeuristicIndex = currentIndex;

        //now update the bgcolor of each cell
        for (int i=0; i<10; i++)
        {
            for (int j=0; j<10; j++)
            {
                this.cells[i][j].updateBackgroundFromHeuristic();
            }
        }
    }

        //this method will deploy the ships randomly in single player mode
    public void deployShips()
    {
        //deploy each ship starting with the ship with the lowest id
        //after deploying each ship, determine if it overlaps with any ships
        //which have a lower id. if it does, deploy it again
        //repeat this process until the ship doesn't overlap with any
        //ships which have a lower id. Then, deploy the next ship.
        for (int i=0; i<5; i++)
        {
            do {
                deployShip(i);
            } while (overlapsWithLower(i));
        }
    }

    //deploys ship with given id
    //assumes that ships with id < given id have already been deployed
    //assumes that the ships which have already been deloyed do not overlap
    //randomly positions ship
    //assures that the ship is completely within the grid
    //does not check for overlap!
    //this method must be called, then overlap should be checked for
    //if overlap is detected, this method will be called again
    //this will be repeated until the ship is positioned with no overlap
    //the orientation of the ship is decided randomly with equal
    //chance to be oriented horizontally or vertically
    public void deployShip(int shipId)
    {
        //choose orientation, equal probability horizontal or vertical
        Orientation orient = (game.rand.nextBoolean()) ?
            Orientation.HORIZONTAL : Orientation.VERTICAL;

        //get a reference to the ship with id shipId
        Ship ship = this.ships[shipId];

        //calculate the rightmost column and row (inclusive) that the
        //ship can be anchored at withought hanging off the grid
        int maxCol, maxRow;

        if (orient == Orientation.HORIZONTAL)
        {
            maxCol = 9 - ship.getLength();
            maxRow = 9;
        }
        else
        {
            maxCol = 9;
            maxRow = 9 - ship.getLength();
        }

        //randomly generate the anchor row and col
        int col = game.rand.nextInt(maxCol + 1);
        int row = game.rand.nextInt(maxRow + 1);

        GridCoordinate coord = new GridCoordinate(col, row);

        System.out.println("deploying ship to " + coord);

        ship.setAnchor(coord);
        ship.setOrientation(orient);

        this.setBackground(Color.pink);
    }
}
